# coding: utf8
"""
Doc: 基于插入时间进行排序的排行榜

new_rank_mode:
原理：拼接 score, Timestamp

     插入过程：传入排行榜名，唯一ID（ID长度要一致),当前时间戳，分数，将Score和Timestamp用乘法拼接，在插入排行榜

     读取某一条记录过程：传入排行榜名，唯一ID，将获取的score分解得到实际score

     读取整个排行榜：直接获取实际排行榜，将获取的score分解得到实际score

     移除某一条记录过程：只移除实际排行榜中的记录
Created on 2015/3/31
@author: MT
"""
import time

import pyshare.distributed.area_lock_define as behavior_lock_define
from pyshare.dal.repository_impl import RoleRepository
from pyshare.utils.user_status_utils import UserStatusUtils
import pyshare.framework.distribute.mutex as mutex
from common.service_config import get_server_id, get_app_id


# 反序时间戳基数，11位
# 原理：当前正序时间戳为10位，用11位减去10位可将后来的时间戳反转为更小的值，从而使其排序在先来的时间戳之后
TIME_BASE = 10000000000

# 时间戳最小值，10位
# 限制时间戳最小值防止时间戳位数变化导致排位混乱
# 从当前时间到最小值还有约8000000000秒，折合253年，余量足够
TIME_BASE_LIMIT = 1000000000


class JoinRankManger(object):
    """
    score 由2个整数条件（score1, score2）拼接的榜单管理基类类
    join_scale：     1.score = score1 * join_scale + score2
                    2.score为实际存入redis的数据
                    3.join_scale必须大于score2的最大取值
                    4.score2 必须 >= 0
    redis存储格式：
                value : object id (比如role_id, alliance_id)
                score : (score1 * join_scale + score2) if score1 >=0 else (score1 * self.join_scale + (score2 - self.join_scale))
    """
    JoinScale = 100000000000

    def __init__(self, join_scale=JoinScale):
        from pyshare.framework.redisutils.redis_storage import RedisStorage
        self.redis = RedisStorage.get_conn('rank')
        self.join_scale = join_scale

    def get_join_score(self, score1, score2):
        if score1 >= 0:
            return score1 * self.join_scale + score2
        else:
            return score1 * self.join_scale + (score2 - self.join_scale)

    @staticmethod
    def fill_ranking_key(key):
        return key.format(srv_id=get_server_id(), app_id=get_app_id())

    def add_to_rank(self, rank_type, value, score1, score2):
        """
        添加单条记录到排行榜
        :param value: rid or alliance_id ...
        :param score1: 第一排序值
        :param score2: 第二排序值
        :param rank_type: 榜单key
        :return:
        """

        self.redis.zadd(rank_type, self.get_join_score(score1, score2), value)

    def add_to_rank_incr(self, rank_type, value, score1, score2):
        """
        往排行榜记录中增加指定数值
        :param value: rid or alliance_id ...
        :param score1: 第一排序值
        :param score2: 第二排序值
        :param rank_type: 榜单key
        :return:
        """

        self.redis.zincrby(rank_type, value, self.get_join_score(score1, score2))

    def add_list_to_rank(self, rank_type, data_list):
        """
        插入多条记录到排行榜
        :param rank_type:
        :param data_list: [[score1, score2, value], [score1, score2, value]]
        :return:
        """
        data = []
        for score1, score2, value in data_list:
            data.append(self.get_join_score(score1, score2))
            data.append(value)

        self.redis.zadd(rank_type, *data)

    def add_set_to_rank(self, rank_type, data_list):
        """
        直接插入多条数据
        :param rank_type:
        :param data_list: [[score, value]]
        :return:
        """
        self.redis.zadd(rank_type, *data_list)

    def get_range(self, rank_type, start=0, end=99):
        """获取榜单记录"""
        data_list = self.redis.zrevrange(rank_type, start, end, True)
        return map(lambda data: [data[0], int(data[1] / self.join_scale)], data_list)

    def get_rank(self, rank_type, value):
        """获取某个玩家排名"""
        rank = self.redis.zrevrank(rank_type, value)
        return rank + 1 if rank is not None else 0

    def get_score(self, rank_type, value):
        """获取某个玩家某个排行榜的分数"""
        score = self.redis.zscore(rank_type, value)
        return int(score / self.join_scale) if score else 0

    def get_set_range(self, rank_type, start=0, end=99):
        return self.redis.zrevrange(rank_type, start, end, True)

    def add_to_set(self, rank_type, data_list):
        self.redis.zadd(rank_type, *data_list)

    def set_expire_time(self, rank_type, expire_time):
        """
        设置TTL
        :param rank_type: set_key
        :param expire_time:
        :return:
        """
        self.redis.expire(rank_type, expire_time)

    def get_number(self, rank_type):
        """获取排行榜数量"""
        return self.redis.zcard(rank_type)

    def del_from_set(self, rank_type, key):
        """
        从set中直接删除某记录
        :param key: set_key
        :param rank_type: set_name
        :return:
        """
        self.redis.zrem(rank_type, key)

    def delete_whole_rank(self, rank_type):
        """删除整个排行榜"""
        self.redis.delete(rank_type)

    def delete_many_whole_rank(self, rank_type_list):
        """删除整个排行榜"""
        self.redis.delete(*rank_type_list)

    def remove_zero(self, rank_type):
        """删除排行榜中为0的记录"""
        self.redis.zremrangebyscore(rank_type, 0, 0)

    def get_set_range_by_score(self, rank_type, teme_stamp, max_score='+inf', min_score=0, start=0, num=100):
        """
        返回rang where score between min and max
        顺序：从大到小
        :param rank_type: 榜单key
        :param max_score: 最大值
        :param min_score: 最小值
        :param start: 切片位置
        :param num: 元素个数
        :return:
        """
        return self.redis.zrevrangebyscore(rank_type,
                                           self.get_join_score(max_score, teme_stamp),
                                           self.get_join_score(min_score, teme_stamp),
                                           start=start, num=num, withscores=True)

    def del_by_out_of_count(self, rank_type, need_count):
        """
        删除超过need_count之后的记录， score从大到小
        :param rank_type: redis key
        :param need_count: 所需的前n名记录， 其余删除
        :return:
        """
        count = self.get_number(rank_type)
        del_max = count - need_count
        if del_max >= 1:
            self.redis.zremrangebyrank('rank_type', 0, del_max - 1)


class NewRankManage(JoinRankManger):
    """
    以new_rank_mode存储方式的RankManager
    以时间为第二排序条件，调用加入榜单方法只需传入第一排序值
    """
    def __init__(self):
        super(NewRankManage, self).__init__()

    @staticmethod
    def get_times_tamp():
        timestamp = TIME_BASE - int(time.time())
        # 限制位数
        if timestamp < TIME_BASE_LIMIT:
            timestamp = TIME_BASE_LIMIT
        return timestamp

    def add_sign_to_rank(self, rank_type, value, score1):
        timestamp = self.get_times_tamp()
        super(NewRankManage, self).add_to_rank(rank_type, value, score1, timestamp)

    def add_sign_to_rank_incr(self, rank_type, value, score1):
        """
        增加排行榜某个角色的分数
        :param rank_type:
        :param value:
        :param score1: 增量
        :return:
        """
        score_base = self.get_score(value, rank_type)
        score = score1 + score_base
        self.add_to_rank(value, score, rank_type, check_status=False, need_lock=False)

    def add_to_rank(self, value, score, rank_type, check_status=True, need_lock=True):
        """添加到排行榜
        代替RankManager的add_to_rank
        1.可选是否检测角色状态
        2.插入时加锁，防止玩家操作和后台冻结角色并发
        :param value: string RoleGuid
        :param score: int 战斗力
        :param rank_type: str 排行榜类型 'FightCap': 战斗力,'Kill': 杀敌数, 'Alliance': 联盟战斗力 ...
        :param check_status bool 是否检查角色状态（是否被封）
        :param need_lock: 是否加锁
        """

        if check_status:
            try:
                map_data = RoleRepository.get_by_rid(value)
                if not map_data:
                    return
                user_status_utils = UserStatusUtils(map_data.user_status)
                if not user_status_utils.can_in_rank():
                    return
            except Exception as e:
                from pyshare.utils.traceback_utils import print_exc
                print_exc(e)
                return

        score = score or 0
        timestamp = self.get_times_tamp()
        # 排行榜中插入新记录
        if need_lock:
            with mutex.KeyMutex(behavior_lock_define.RankLock.U_RANK_MODIFY_LOCK.format(RoleGuid=value)):
                super(NewRankManage, self).add_to_rank(rank_type, value, score, timestamp)
        else:
            super(NewRankManage, self).add_to_rank(rank_type, value, score, timestamp)

    def del_from_rank(self, value, rank_type):
        """
        从排行榜中删除某记录
        :param value:玩家排行榜(RoleGuid)/联盟排行榜(AllianceId)
        :param rank_type:str 排行榜类型 'FightCap': 战斗力,'Kill': 杀敌数, 'Alliance': 联盟战斗力
        """
        with mutex.KeyMutex(behavior_lock_define.RankLock.U_RANK_MODIFY_LOCK.format(RoleGuid=value)):
            self.del_from_set(rank_type, value)

    def get_rank(self, value, rank_type):
        """获取某个玩家排名"""
        return super(NewRankManage, self).get_rank(rank_type, value)

    def get_score(self, value, rank_type):
        """获取某个玩家某个排行榜的分数"""
        return super(NewRankManage, self).get_score(rank_type, value)

    def get_set_score(self, value, rank_type):
        """获取set中某个记录的score，不作额外处理"""
        score = self.redis.zscore(rank_type, value)
        return int(score) if score else 0

    def get_set_rank(self, value, rank_type):
        """获取某个玩家某个排行榜的排名"""
        rank = self.redis.zrevrank(rank_type, value)
        return rank + 1 if rank is not None else 0

    def get_set_range_by_score(self, rank_type, max_score='+inf', min_score=0, start=0, num=100):
        """
        返回rang where score between min and max
        顺序：从大到小
        :param rank_type: 榜单key
        :param max_score: 最大值
        :param min_score: 最小值
        :param start: 切片位置
        :param num: 元素个数
        :return:
        """
        timestamp = self.get_times_tamp()
        return super(NewRankManage, self).get_set_range_by_score(rank_type, timestamp,
                                                                 max_score, min_score, start=start, num=num)

    def update_all_rank(self):
        """更新所有排行榜"""
        # 暂时无用
        print "Update All Rank..."

    def update_fight_cap_rank(self):
        """更新战力榜"""
        # 暂时无用
        # user_status_utils = UserStatusUtils()
        # for mapData in RoleAdapter.objects.all():
        #     # 已冻结的角色，不能放入排行榜
        #     user_status_utils.set_status(mapData.UserStatus)
        #     if user_status_utils.can_in_rank():
        #         self.add_to_rank(mapData.RoleGuid, mapData.FightCapacity, 'FightCap')

    def update_alliance_rank(self):
        """更新联盟战力榜"""
        # 暂时无用
        # for alliance in models.TbAlliance.objects.all():
        #     self.add_to_rank(alliance.Id, alliance.FightPower, 'Alliance', check_status=False)

    def set_rank_exist(self, rank_key, seconds):
        """设置过期时间"""
        self.redis.expire(rank_key, seconds)


new_rankManager = NewRankManage()


def get_rank_manager():
    return new_rankManager

if __name__ == "__main__":
    # rankManager.addToRank(6, 102, 'FightCap')
    # rankManager.addToRank('test2', 102)
    # rankManager.addToRank('test3', 121)
    # rankManager.addToRank('test5', 14)
    # rankManager.addToRank('test11', 1)
    # rankManager.addToRank('test212', 11)
    # print rankManager.redis.zrem(u'rank', 100)

    a = 100000000
    # rankManager.add_to_rank("ddd", a, "TEST")
    # time.sleep(1)
    # rankManager.add_to_rank("bbb", a, "TEST")
    # time.sleep(2)
    # rankManager.add_to_rank("ccc", a+500, "TEST")

    # print rankManager.del_from_rank("ccc", "TEST")

    print ("================================")

    # print rankManager.getRank(2, 'FightExp')
